/*
 * Copyright (C) 2012-2013  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.remote.rsa.message.data;

import java.io.IOException;
import java.lang.reflect.Method;

import nemos.remote.util.RemoteUtil;

import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.service.remoteserviceadmin.EndpointDescription;

/**
 * InvocationMessage : The remote invocation message.
 * 
 * @author Romain DUBOIS
 */
public class InvocationMessage extends Message {

    /** Serial version */
    private static final long serialVersionUID = 1L;

    /** The signature separator */
    private static final String SIGNATURE_SEPARATOR = ","; //$NON-NLS-1$

    /**
     * Build the method signature (for remote calls)
     * 
     * @param pMethod
     *            The method to serialize
     * @return The method signature
     */
    public static String getSignature(final Method pMethod) {
        final StringBuilder lBuilder = new StringBuilder();
        lBuilder.append(pMethod.getName());
        if (pMethod.getParameterTypes().length > 0) {
            for (final Class<?> lType : pMethod.getParameterTypes()) {
                lBuilder.append(SIGNATURE_SEPARATOR);
                lBuilder.append(lType.getName());
            }
        }

        return lBuilder.toString();
    }

    /**
     * Find a method from its signature.
     * 
     * @param pSignature
     *            The method's signature
     * @param pClass
     *            The class' method
     * @return The method
     * @throws NoSuchMethodException
     *             If the method is not found
     */
    public static Method getMethod(final String pSignature, final Class<?> pClass)
            throws NoSuchMethodException {
        final String[] lParts = pSignature.split(SIGNATURE_SEPARATOR);
        final String lMethodName = lParts[0];
        for (final Method lMethod : pClass.getMethods()) {
            if (lMethod.getName().equals(lMethodName)) {
                // Check parameters
                boolean lMatches = lParts.length == lMethod.getParameterTypes().length + 1;
                int lIndex = 1;
                while (lMatches && lIndex < lParts.length) {
                    lMatches = lParts[lIndex].equals(lMethod.getParameterTypes()[lIndex - 1]
                            .getName());
                    lIndex++;
                }

                if (lMatches) {
                    return lMethod;
                }
            }
        }
        throw new NoSuchMethodException(pSignature);
    }

    /** The calling framework UUID */
    private final String mCallingFrameworkUuid;
    /** The called service famework UUID */
    private final String mCalledFrameworkUuid;
    /** The called service ID */
    private final long mServiceId;
    /** The called method class name */
    private final String mClassName;
    /** The called method signature */
    private final String mMethodSignature;

    /**
     * Constructor
     * 
     * @param pEndpoint
     *            The called endpoint
     * @param pMethod
     *            The called method
     * @param pArgs
     *            The call arguments
     */
    public InvocationMessage(final EndpointDescription pEndpoint, final Method pMethod,
            final Object[] pArgs) {
        super(pArgs);

        mCallingFrameworkUuid = RemoteUtil.getFrameworkUuid();
        mCalledFrameworkUuid = pEndpoint.getFrameworkUUID();
        mServiceId = pEndpoint.getServiceId();
        mClassName = pMethod.getDeclaringClass().getName();
        mMethodSignature = getSignature(pMethod);
    }

    /**
     * Get the call arguments.
     * 
     * @param pBundle
     *            The service bundle
     * @return The call arguments
     * @throws ClassNotFoundException
     *             If an argument class is unknown in the service bundle class
     *             space
     * @throws IOException
     *             If a deserialization failed
     */
    public Object[] getArgs(final Bundle pBundle) throws IOException, ClassNotFoundException {
        return (Object[]) getData(pBundle);
    }

    /**
     * Get the calling framework UUID.
     * 
     * @return The calling framework UUID
     */
    public String getCallingFrameworkUuid() {
        return mCallingFrameworkUuid;
    }

    /**
     * Get the called service framework UUID.
     * 
     * @return The target framework UUID
     */
    public String getCalledFrameworkUuid() {
        return mCalledFrameworkUuid;
    }

    /**
     * Get the called method.
     * 
     * @param pBundle
     *            The called service bundle
     * @return The called method
     * @throws NoSuchMethodException
     *             If the method can not be found
     * @throws ClassNotFoundException
     *             If the method's class can not be found
     */
    public Method getMethod(final Bundle pBundle) throws NoSuchMethodException,
            ClassNotFoundException {
        return getMethod(mMethodSignature, getMethodClass(pBundle));
    }

    /**
     * Get the called method's class.
     * 
     * @param pBundle
     *            The called service bundle
     * @return The called method's class
     * @throws ClassNotFoundException
     *             If the method's class can not be found
     */
    public Class<?> getMethodClass(final Bundle pBundle) throws ClassNotFoundException {
        return pBundle.loadClass(mClassName);
    }

    /**
     * Get the called service ID.
     * 
     * @return The service ID
     * 
     * @see Constants#SERVICE_ID
     */
    public long getServiceId() {
        return mServiceId;
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.remote.rsa.message.data.Message#toString()
     */
    @Override
    public String toString() {
        final StringBuilder lBuilder = new StringBuilder(super.toString());
        lBuilder.append('[');
        lBuilder.append(mServiceId).append('@').append(mCalledFrameworkUuid);
        lBuilder.append('>');
        lBuilder.append(mClassName).append('.').append(mMethodSignature);
        lBuilder.append(']');
        return lBuilder.toString();
    }
}
